package com.wangjiacai.hqlQuery.repository.impl;

import com.wangjiacai.hqlQuery.repository.BaseRepository;
import com.wangjiacai.hqlQuery.util.BeanUtil;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Query;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

public abstract class AbstracterBaseRepositoryImpl implements BaseRepository {

    abstract Query createQuery(String hql);

    abstract Query createNativeQuery(String sql);

    private <T> T getFirstResult(List<T> ls) {
        if (ls != null && ls.size() > 0) {
            return ls.get(0);
        }
        return null;
    }

    private <T> List<T> transform(List<Object[]> list, Class<T> T) {
        List<T> result = new ArrayList<>();
        for (Object[] obj : list) {
            Constructor<?>[] constructors = T.getConstructors();
            try {
                for (Constructor constructor : constructors) {
                    if (constructor.getParameterCount() == obj.length) {
                        T t = (T) constructor.newInstance(obj);
                        result.add(t);
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    @Override
    public <T> T get(String hql, Class<T> T) {
        List<T> ls = find(hql, T);
        return getFirstResult(ls);
    }

    @Override
    public <T> T get(String hql, Object object, Class<T> T) {
        List<T> ls = find(hql, object, T);
        return getFirstResult(ls);
    }

    @Override
    public <T> T get(String hql, Map<String, Object> params, Class<T> T) {
        List<T> ls = find(hql, params, T);
        return getFirstResult(ls);
    }

    protected void setProperties(Query query, Object ob) {
        query.getParameters().forEach(property -> {
            try {
                query.setParameter(property.getName(), BeanUtil.getPropertyValue(ob, property.getName()));
            } catch (Exception e) {
            }
        });
    }

    protected void setProperties(Query query, Map<String, Object> map) {
        query.getParameters().forEach(property -> {
            try {
                query.setParameter(property.getName(), map.get(property.getName()));
            } catch (Exception e) {
            }
        });
    }

    @Override
    public <T> List<T> find(String hql, Class<T> T) {
        Query query = createQuery(hql);
        return query.getResultList();
    }


    @Override
    public <T> List<T> find(String hql, Object object, Class<T> T) {
        Query query = createQuery(hql);
        setProperties(query, object);
        return query.getResultList();
    }

    @Override
    public <T> List<T> find(String hql, Map<String, Object> map, Class<T> T) {
        Query query = createQuery(hql);
        setProperties(query, map);
        return query.getResultList();
    }

    @Override
    public <T> List<T> nativeFind(String sql, Class<T> T) {
        Query query = createNativeQuery(sql);
        List<Object[]> list = query.getResultList();
        return transform(list, T);
    }

    @Override
    public <T> List<T> nativeFind(String sql, Map<String, Object> map, Class<T> T) {
        Query query = createNativeQuery(sql);
        setProperties(query, map);
        List<Object[]> list = query.getResultList();
        return transform(list, T);
    }

    @Override
    public <T> List<T> nativeFind(String sql, Object object, Class<T> T) {
        Query query = createNativeQuery(sql);
        setProperties(query, object);
        List<Object[]> list = query.getResultList();
        return transform(list, T);
    }

    @Override
    public <T> List<T> nativeFind(String sql, Map<String, Object> map, Pageable pageable, Class<T> T) {
        List<Object[]> list = nativeFind(pageable, sql, query -> setProperties(query, map));
        return transform(list, T);
    }

    @Override
    public <T> List<T> nativeFind(String sql, Object object, Pageable pageable, Class<T> T) {
        List<Object[]> list = nativeFind(pageable, sql, query -> setProperties(query, object));
        return transform(list, T);
    }

    @Override
    public long count(String hql, Object object) {
        Query query = createQuery(hql);
        setProperties(query, object);
        return (long) query.getSingleResult();
    }

    @Override
    public long count(String hql, Map<String, Object> map) {
        Query query = createQuery(hql);
        setProperties(query, map);
        return (long) query.getSingleResult();
    }

    @Override
    public long count(String hql) {
        Query query = createQuery(hql);
        return (long) query.getSingleResult();
    }

    @Override
    public long nativeCount(String sql) {
        Query query = createNativeQuery(sql);
        return ((BigInteger)(query.getSingleResult())).longValue();
    }

    @Override
    public long nativeCount(String sql, Object object) {
        Query query = createNativeQuery(sql);
        setProperties(query, object);
        return ((BigInteger)(query.getSingleResult())).longValue();
    }

    @Override
    public long nativeCount(String sql, Map<String, Object> params) {
        Query query = createNativeQuery(sql);
        setProperties(query, params);
        return ((BigInteger)(query.getSingleResult())).longValue();
    }

    protected List find(Pageable pageable, String hql, Consumer<Query> consumer) {
        Sort sort = pageable.getSort();
        if (pageable.isPaged() && sort.isSorted()) {
            hql += " order by ";
            Iterator<Sort.Order> iterator = sort.iterator();
            while (iterator.hasNext()) {
                Sort.Order order = iterator.next();
                hql += order.getProperty() + " " + order.getDirection() + ",";
            }
            hql = hql.substring(0, hql.length() - 1);
        }
        Query query = createQuery(hql);
        consumer.accept(query);
        int size = pageable.getPageSize();
        int pageNumber = pageable.getPageNumber();
        return query.setFirstResult(size * pageNumber).setMaxResults(size).getResultList();
    }

    protected List nativeFind(Pageable pageable, String hql, Consumer<Query> consumer) {
        Sort sort = pageable.getSort();
        if (pageable.isPaged() && sort.isSorted()) {
            hql += " order by ";
            Iterator<Sort.Order> iterator = sort.iterator();
            while (iterator.hasNext()) {
                Sort.Order order = iterator.next();
                hql += order.getProperty() + " " + order.getDirection() + ",";
            }
            hql = hql.substring(0, hql.length() - 1);
        }
        Query query = createNativeQuery(hql);
        consumer.accept(query);
        int size = pageable.getPageSize();
        int pageNumber = pageable.getPageNumber();
        return query.setFirstResult(size * pageNumber).setMaxResults(size).getResultList();
    }



    @Override
    public <T> List<T> find(String hql, Object object, Pageable pageable, Class<T> T) {
        return find(pageable, hql, query -> setProperties(query, object));
    }

    @Override
    public <T> List<T> find(String hql, Map<String, Object> map, Pageable pageable, Class<T> T) {
        return find(pageable, hql, query -> map.forEach((key, value) -> query.setParameter(key, value)));
    }

    @Override
    @Transactional
    public void execute(String hql) {
        Query query = createQuery(hql);
        query.executeUpdate();
    }

    @Override
    @Transactional
    public void execute(String hql, Object object) {
        Query query = createQuery(hql);
        setProperties(query, object);
        query.executeUpdate();
    }

    @Override
    @Transactional
    public void execute(String hql, Map<String, Object> map) {
        Query query = createQuery(hql);
        setProperties(query, map);
        query.executeUpdate();
    }
}
